Skip to content

test(ios-readplace): make the share extension logic testable#921

Closed
FagnerMartinsBrack wants to merge 2 commits into
ios/independent-contract-fixesfrom
ios/share-extension-testable
Closed

test(ios-readplace): make the share extension logic testable#921
FagnerMartinsBrack wants to merge 2 commits into
ios/independent-contract-fixesfrom
ios/share-extension-testable

Conversation

@FagnerMartinsBrack

Copy link
Copy Markdown
Member

Stacked on #920 (base = ios/coverage-gate). Review/merge after it.

Purpose

ShareViewController and ShareURLExtractor were compiled only into the extension target, which no test target sees. So their real branching logic — the multi-item payload scan, the URL-item type fallbacks (URL / Data / String / plain-text), the PDF size ceiling, and the outcome→status mapping — had zero coverage and was structurally untestable. A host delivering an unexpected payload shape would regress silently.

What changed & the value

  • ShareURLExtractorShared/: it is Foundation-only, so moving it makes it compile into the app target that @testable import Readplace can reach. extract(from:) is split into a thin NSExtensionContext wrapper plus an item-list core a test drives with fabricated NSItemProviders — no live extension context (which no test can construct) required.
  • Extract ShareStatusPresentation: the seven-case outcome→(message, SF Symbol, tone) decision — including joining a refusal's messages and choosing error-vs-warning — moves out of the UIKit controller into a pure value in Shared/. ShareViewController keeps only the trivial tone→UIColor mapping at the boundary.
  • Coverage: ShareURLExtractor now at 96% (URL/Data/String/plain-text extraction, multi-item split, PDF provider + suggested-name fallback, the byte ceiling), ShareStatusPresentation at 100% (every outcome, the refusal join, both tone branches).
  • Baseline (scripts/coverage-baseline.json): ShareURLExtractor moves out of the exclusions to a 96% floor; ShareStatusPresentation pinned at 100%. The gate now enforces 21 files (was 19), 9 excluded (was 10).

Testing

Full suite green with the gate active locally (227 tests, +20; iOS coverage gate passed: 21 files at or above floor, 9 excluded), plus the STAGING smoke pass.

The rest of the repo enforces a 100% coverage gate, but the iOS suite ran with
no coverage measurement or threshold at all — a structural blind spot. `make
test` now runs with `-enableCodeCoverage` and fails if any source file drops
below its recorded floor (`scripts/check-coverage.py`).

The gate is a ratchet, not an all-at-once 100%: `scripts/coverage-baseline.json`
excludes pure SwiftUI views and WebKit/OS-boundary glue (the Swift analog of the
repo's whole-file OS-boundary exclusion) and records a per-file floor for every
other file, seeded at today's coverage. A file with no floor must be 100%, so a
new logic file cannot land untested. Later PRs raise the floors toward 100% and
move ShareURLExtractor out of the exclusions once it is testable.

The checker uses only the system python3 + xcrun, so the CI ios-tests job needs
no extra toolchain setup.
`ShareViewController` and `ShareURLExtractor` lived only in the extension
target, which no test target compiles, so their real logic — the multi-item
payload scan, the URL-item type fallbacks, the PDF size ceiling, and the
outcome→status mapping — had zero coverage and was structurally unreachable.

- Move `ShareURLExtractor` into `Shared/` (it is Foundation-only) so it compiles
  into the app target `@testable import` can reach, and split `extract` into a
  context wrapper plus an item-list core a test can drive with fabricated
  `NSItemProvider`s instead of a live extension context.
- Extract the outcome→(message, symbol, tone) decision out of `ShareViewController`
  into a pure `ShareStatusPresentation` in `Shared/`; the controller keeps only
  the tone→`UIColor` mapping at the UIKit boundary.
- Cover both: `ShareURLExtractor` at 96% (URL / data / string / plain-text
  extraction, multi-item split, PDF provider + suggested-name fallback, the byte
  ceiling) and `ShareStatusPresentation` at 100% (every outcome, refusal join,
  error-vs-warning tone).
- Coverage baseline: drop `ShareURLExtractor` from the exclusions and floor it at
  96%; pin `ShareStatusPresentation` at 100%.
Base automatically changed from ios/coverage-gate to ios/independent-contract-fixes July 5, 2026 12:57
@FagnerMartinsBrack

Copy link
Copy Markdown
Member Author

Superseded by #927, which consolidates all 9 stacked PRs into one PR targeting main (so CI runs and it's independently deployable). See #927 for the full description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant